﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace WJP.Reflection
{
    /// <summary>
    /// 本类提供了使用 Emit 方式来高速调用字段、属性、方法的一些方法。
    /// 所有调用过的被生成的方法，都会被存储起来，以方便再次调用。
    /// </summary>
    public static class MethodCaller
    {
        private static readonly Dictionary<MethodCacheKey, DynamicMemberHandle> _memberCache = new Dictionary<MethodCacheKey, DynamicMemberHandle>();
        private static Dictionary<MethodCacheKey, DynamicMethodHandle> _methodCache = new Dictionary<MethodCacheKey, DynamicMethodHandle>();
        private const BindingFlags propertyFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy;
        private const BindingFlags fieldFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
        private const BindingFlags allLevelFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy;
        private const BindingFlags oneLevelFlags = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
        private const BindingFlags ctorFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

        /// <summary>Gets the store count.</summary>
        /// <value>The store count.</value>
        public static int StoreCount
        {
            get
            {
                return _memberCache.Count + _methodCache.Count;
            }
        }

        /// <summary>Clears the store.</summary>
        public static void ClearStore()
        {
            _memberCache.Clear();
            _methodCache.Clear();
        }

        /// <summary>
        /// Invokes a property getter using dynamic
        /// method invocation.
        /// </summary>
        /// <param name="obj">Target object.</param>
        /// <param name="property">Property to invoke.</param>
        /// <returns></returns>
        public static object CallPropertyGetter(object obj, string property)
        {
            return GetCachedProperty(obj.GetType(), property).DynamicMemberGet(obj);
        }

        /// <summary>
        /// Invokes a property setter using dynamic
        /// method invocation.
        /// </summary>
        /// <param name="obj">Target object.</param>
        /// <param name="property">Property to invoke.</param>
        /// <param name="value">New value for property.</param>
        public static void CallPropertySetter(object obj, string property, object value)
        {
            GetCachedProperty(obj.GetType(), property).DynamicMemberSet(obj, value);
        }

        internal static DynamicMemberHandle GetCachedProperty(Type objectType, string propertyName)
        {
            MethodCacheKey key = new MethodCacheKey(objectType, propertyName, new Type[1]
            {
                typeof (object)
            });
            DynamicMemberHandle dynamicMemberHandle = null;
            if (!_memberCache.TryGetValue(key, out dynamicMemberHandle))
            {
                lock (_memberCache)
                {
                    if (!_memberCache.TryGetValue(key, out dynamicMemberHandle))
                    {
                        PropertyInfo property = objectType.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy);
                        if (property == null)
                            throw new InvalidOperationException(string.Format("Member not found on object ({0})", (object)propertyName));
                        dynamicMemberHandle = new DynamicMemberHandle(property);
                        _memberCache.Add(key, dynamicMemberHandle);
                    }
                }
            }
            return dynamicMemberHandle;
        }

        internal static DynamicMemberHandle GetCachedField(Type objectType, string fieldName)
        {
            MethodCacheKey key = new MethodCacheKey(objectType, fieldName, new Type[1]
            {
                typeof (object)
            });
            DynamicMemberHandle dynamicMemberHandle = null;
            if (!_memberCache.TryGetValue(key, out dynamicMemberHandle))
            {
                lock (_memberCache)
                {
                    if (!_memberCache.TryGetValue(key, out dynamicMemberHandle))
                    {
                        FieldInfo field = objectType.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
                        if (field == null)
                            throw new InvalidOperationException(string.Format("Resources.MemberNotFoundException:{0}", fieldName));
                        dynamicMemberHandle = new DynamicMemberHandle(field);
                        _memberCache.Add(key, dynamicMemberHandle);
                    }
                }
            }
            return dynamicMemberHandle;
        }

        /// <summary>
        /// 使用反射动态调用一个方法。
        /// 如果指定对象已经实现了这个方法，则直接调用，否则直接返回。
        /// </summary>
        /// <param name="obj">The object.</param>
        /// <param name="method">The method.</param>
        /// <param name="parameters">
        /// 方法的参数数组。
        /// 注意，为了能尽量正确地找到方法，数组中的每一个元素应该尽量不是 null。如果有参数传入 null 时，可以传入参数的类型来代替。
        /// 在有任意一个参数的类型未指定的情况下，会对方法进行模糊匹配，同时不再对找到的结果方法 Emit 生成方法并缓存。
        /// </param>
        /// <param name="result">如果成功调用，则返回方法的返回值。</param>
        /// <returns>返回是否在目标对象上成功调用了该方法。</returns>
        public static bool CallMethodIfImplemented(object obj, string method, object[] parameters, out object result)
        {
            return CallMethodCore(obj, method, parameters, false, out result);
        }

        /// <summary>
        /// Uses reflection to dynamically invoke a method,
        /// throwing an exception if it is not
        /// implemented on the target object.
        /// </summary>
        /// <param name="obj">Object containing method.</param>
        /// <param name="method">Name of the method.</param>
        /// <param name="parameters">Parameters to pass to method.</param>
        public static object CallMethod(object obj, string method, params object[] parameters)
        {
            object result = (object)null;
            CallMethodCore(obj, method, parameters, true, out result);
            return result;
        }

        /// <summary>
        /// Uses reflection to dynamically invoke a method,
        /// throwing an exception if it is not
        /// implemented on the target object.
        /// </summary>
        /// <param name="obj">Object containing method.</param>
        /// <param name="info">MethodInfo for the method.</param>
        /// <param name="parameters">Parameters to pass to method.</param>
        public static object CallMethod(object obj, MethodInfo info, params object[] parameters)
        {
            DynamicMethodHandle cachedMethod = GetCachedMethod(info);
            if (cachedMethod == null || cachedMethod.DynamicMethod == null)
                throw new InvalidProgramException(info.Name + " 方法没有实现。");
            return CallMethod(obj, cachedMethod, parameters);
        }

        /// <summary>
        /// Uses reflection to dynamically invoke a method,
        /// throwing an exception if it is not implemented
        /// on the target object.
        /// </summary>
        /// <param name="obj">Object containing method.</param>
        /// <param name="methodHandle">MethodHandle for the method.</param>
        /// <param name="parameters">Parameters to pass to method.</param>
        private static object CallMethod(object obj, DynamicMethodHandle methodHandle, params object[] parameters)
        {
            DynamicMethodDelegate dynamicMethod = methodHandle.DynamicMethod;
            object[] args = parameters == null ? new object[1] : parameters;
            if (methodHandle.HasFinalArrayParam)
            {
                bool flag = true;
                int methodParamsLength = methodHandle.MethodParamsLength;
                if (parameters.Length == methodParamsLength && parameters[methodParamsLength - 1] is Array)
                    flag = false;
                if (flag)
                {
                    object[] objArray = new object[methodParamsLength];
                    Array.Copy(args, objArray, methodParamsLength - 1);
                    int length = args.Length - (methodParamsLength - 1);
                    Array instance = Array.CreateInstance(methodHandle.FinalArrayElementType, length);
                    Array.Copy(args, methodParamsLength - 1, instance, 0, length);
                    objArray[objArray.Length - 1] = instance;
                    args = objArray;
                }
            }
            return methodHandle.DynamicMethod(obj, args);
        }

        /// <summary>
        /// 使用反射动态调用一个方法。
        /// 如果指定对象已经实现了这个方法，则直接调用，否则直接返回。
        /// </summary>
        /// <param name="obj">The object.</param>
        /// <param name="method">The method.</param>
        /// <param name="parameters">方法的参数数组。
        /// 注意，为了能尽量正确地找到方法，数组中的每一个元素应该尽量不是 null。如果有参数传入 null 时，可以传入参数的类型来代替。
        /// 在有任意一个参数的类型未指定的情况下，会对方法进行模糊匹配。</param>
        /// <param name="errorIfNotFound">如果没有找到对应的方法，是否需要抛出异常。</param>
        /// <param name="result">如果成功调用，则返回方法的返回值。</param>
        /// <returns>返回是否在目标对象上成功调用了该方法。</returns>
        /// <exception cref="T:System.InvalidProgramException"></exception>
        private static bool CallMethodCore(object obj, string method, object[] parameters, bool errorIfNotFound, out object result)
        {
            result = null;
            bool isVoid = false;
            Type[] paramters = new Type[parameters.Length];
            for (int index = 0; index < parameters.Length; ++index)
            {
                object parameter = parameters[index];
                if (parameter == null)
                    isVoid = true;
                else if (parameter is NullParameter)
                {
                    paramters[index] = (parameter as NullParameter).ParameterType;
                    parameters[index] = null;
                }
                else
                    paramters[index] = parameter.GetType();
            }
            bool callSuccess = false;
            List<MethodInfo> methodInfoList = null;
            Type objType = obj.GetType();
            if (!isVoid)
            {
                DynamicMethodHandle cachedMethod = GetCachedMethod(objType, method, paramters);
                if (cachedMethod != null && cachedMethod.DynamicMethod != null)
                {
                    callSuccess = true;
                    result = CallMethod(obj, cachedMethod, parameters);
                }
            }
            else
            {
                List<MethodInfo> methodsOnNullParam = FindMethodsOnNullParam(objType, method, paramters);
                if (methodsOnNullParam.Count == 1)
                {
                    DynamicMethodHandle cachedMethod = GetCachedMethod(methodsOnNullParam[0]);
                    if (cachedMethod != null && cachedMethod.DynamicMethod != null)
                    {
                        callSuccess = true;
                        result = CallMethod(obj, cachedMethod, parameters);
                    }
                }
                else if (methodsOnNullParam.Count > 1)
                {
                    foreach (Type type2 in TypeExtension.GetHierarchy(objType))
                    {
                        Type type = type2;
                        IEnumerable<MethodInfo> source = methodsOnNullParam.Where(p => p.DeclaringType == type);
                        if (source.Count() == 1)
                        {
                            DynamicMethodHandle cachedMethod = GetCachedMethod(source.First());
                            if (cachedMethod != null && cachedMethod.DynamicMethod != null)
                            {
                                callSuccess = true;
                                result = CallMethod(obj, cachedMethod, parameters);
                                break;
                            }
                        }
                    }
                    if (!callSuccess)
                        methodInfoList = methodsOnNullParam;
                }
            }
            if (errorIfNotFound)
            {
                if (methodInfoList != null)
                {
                    StringBuilder stringBuilder = new StringBuilder();
                    stringBuilder.AppendFormat("在类型 {0} 中找到多个匹配的方法：", objType);
                    foreach (MethodInfo methodInfo in methodInfoList)
                        stringBuilder.AppendLine(methodInfo.ToString());
                    throw new InvalidProgramException(stringBuilder.ToString());
                }
                if (!callSuccess)
                {
                    StringBuilder stringBuilder = new StringBuilder();
                    stringBuilder.AppendFormat("在类型 {0} 中没有找到方法：{1}(", objType, method);
                    int index2 = 0;
                    for (int length = paramters.Length; index2 < length; ++index2)
                    {
                        if (index2 > 0)
                            stringBuilder.Append(',');
                        Type type2 = paramters[index2];
                        stringBuilder.Append(type2 != (Type)null ? paramters[index2].Name : "null");
                    }
                    stringBuilder.Append(")。");
                    throw new InvalidProgramException(stringBuilder.ToString());
                }
            }
            return callSuccess;
        }

        private static DynamicMethodHandle GetCachedMethod(MethodInfo info)
        {
            MethodCacheKey key = new MethodCacheKey(info);
            DynamicMethodHandle dynamicMethodHandle = (DynamicMethodHandle)null;
            if (!_methodCache.TryGetValue(key, out dynamicMethodHandle))
            {
                lock (_methodCache)
                {
                    if (!_methodCache.TryGetValue(key, out dynamicMethodHandle))
                    {
                        dynamicMethodHandle = new DynamicMethodHandle(info);
                        _methodCache.Add(key, dynamicMethodHandle);
                    }
                }
            }
            return dynamicMethodHandle;
        }

        /// <summary>
        /// 获取动态代理方法并添加缓存
        /// </summary>
        /// <param name="objType">对象类型</param>
        /// <param name="method">方法名</param>
        /// <param name="parameters">参数</param>
        /// <returns></returns>
        private static DynamicMethodHandle GetCachedMethod(Type objType, string method, params Type[] parameters)
        {
            MethodCacheKey key = new MethodCacheKey(objType, method, parameters);
            DynamicMethodHandle dynamicMethodHandle = null;
            if (!_methodCache.TryGetValue(key, out dynamicMethodHandle))
            {
                MethodInfo dynamicMethod = GetMethod(objType, method, parameters);
                if (dynamicMethod != null)
                {
                    lock (_methodCache)
                    {
                        if (!_methodCache.TryGetValue(key, out dynamicMethodHandle))
                        {
                            dynamicMethodHandle = new DynamicMethodHandle(dynamicMethod);
                            _methodCache.Add(key, dynamicMethodHandle);
                        }
                    }
                }
            }
            return dynamicMethodHandle;
        }

        /// <summary>如果参数列表中存在 null 类型时，使用模糊的匹配方式。</summary>
        /// <param name="objType"></param>
        /// <param name="methodName"></param>
        /// <param name="paramters"></param>
        /// <returns></returns>
        private static List<MethodInfo> FindMethodsOnNullParam(Type objType, string methodName, Type[] paramters)
        {
            List<MethodInfo> methodInfoList = new List<MethodInfo>();
            for (Type type = objType; type != null; type = type.BaseType)
            {
                MethodInfo[] methods = type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
                int index1 = 0;
                for (int length1 = methods.Length; index1 < length1; ++index1)
                {
                    MethodInfo methodInfo = methods[index1];
                    if (methodInfo.Name == methodName)
                    {
                        ParameterInfo[] parameters = methodInfo.GetParameters();
                        if (!IsParamArray(parameters))
                        {
                            if (parameters.Length == paramters.Length)
                            {
                                bool flag = true;
                                int index2 = 0;
                                for (int length2 = paramters.Length; index2 < length2; ++index2)
                                {
                                    if (!IsParamTypeMatched(paramters[index2], parameters[index2].ParameterType))
                                        flag = false;
                                }
                                if (flag)
                                    methodInfoList.Add(methodInfo);
                            }
                        }
                        else if (paramters.Length >= parameters.Length - 1)
                        {
                            bool flag = true;
                            int index2 = 0;
                            for (int index3 = parameters.Length - 1; index2 < index3; ++index2)
                            {
                                if (!IsParamTypeMatched(paramters[index2], parameters[index2].ParameterType))
                                    flag = false;
                            }
                            if (flag)
                            {
                                parameters[parameters.Length - 1].ParameterType.GetElementType();
                                int index3 = parameters.Length - 1;
                                for (int length2 = paramters.Length; index3 < length2; ++index3)
                                {
                                    if (!IsParamTypeMatched(paramters[index3], parameters[index3].ParameterType))
                                        flag = false;
                                }
                            }
                            if (flag)
                                methodInfoList.Add(methodInfo);
                        }
                    }
                }
            }
            return methodInfoList;
        }

        /// <summary>判断指定的实参是否与方法中的某形参匹配。</summary>
        /// <param name="parameterType">实参类型。</param>
        /// <param name="targetType">形参的类型。</param>
        /// <returns></returns>
        private static bool IsParamTypeMatched(Type parameterType, Type targetType)
        {
            if (parameterType == (Type)null)
                return targetType.IsClass || TypeExtension.IsNullable(targetType);
            return targetType.IsAssignableFrom(parameterType) || !parameterType.IsClass && TypeExtension.IgnoreNullable(targetType).IsAssignableFrom(parameterType);
        }

        /// <summary>
        /// Uses reflection to locate a matching method
        /// on the target object.
        /// </summary>
        /// <param name="objectType">Type of object containing method.</param>
        /// <param name="method">Name of the method.</param>
        /// <param name="parameters">Parameters to pass to method.</param>
        /// <returns></returns>
        /// <exception cref="T:System.InvalidProgramException"></exception>
        private static MethodInfo GetMethod(Type objectType, string method, params Type[] parameters)
        {
            return FindMethod(objectType, method, parameters);
        }

        private static MethodInfo FindMethodUsingFuzzyMatching(Type objectType, string method, Type[] parameters)
        {
            int length1 = parameters.Length;
            Type type = objectType;
            do
            {
                MethodInfo[] methods = type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
                int index1 = 0;
                for (int length2 = methods.Length; index1 < length2; ++index1)
                {
                    MethodInfo methodInfo = methods[index1];
                    if (methodInfo.Name == method)
                    {
                        ParameterInfo[] parameters1 = methodInfo.GetParameters();
                        int length3 = parameters1.Length;
                        if (length3 > 0 && length1 >= length3 - 1 && IsLastArray(parameters1))
                            return methodInfo;
                    }
                }
                int index2 = 0;
                for (int length2 = methods.Length; index2 < length2; ++index2)
                {
                    MethodInfo methodInfo = methods[index2];
                    if (methodInfo.Name == method && methodInfo.GetParameters().Length == length1)
                        return methodInfo;
                }
                type = type.BaseType;
            }
            while (type != null);
            return null;
        }

        /// <summary>
        /// 查找指定类型上对应参数类型的指定方法。
        /// 从子类到基类逐个检查。同时，也检测私有的方法。
        /// </summary>
        /// <param name="objectType">包含这个方法的类型。</param>
        /// <param name="method">方法名。</param>
        /// <param name="types">方法的所有参数类型。</param>
        public static MethodInfo FindMethod(Type objectType, string method, Type[] types)
        {
            MethodInfo method1;
            do
            {
                method1 = objectType.GetMethod(method, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, types, null);
                if (!(method1 != null))
                    objectType = objectType.BaseType;
                else
                    break;
            }
            while (objectType != null);
            return method1;
        }

        /// <summary>
        /// Returns information about the specified
        /// method, finding the method based purely
        /// on the method name and number of parameters.
        /// </summary>
        /// <param name="objectType">Type of object containing method.</param>
        /// <param name="method">Name of the method.</param>
        /// <param name="parameterCount">
        /// Number of parameters to pass to method.
        /// </param>
        private static MethodInfo FindMethod(Type objectType, string method, int parameterCount)
        {
            MethodInfo methodInfo = (MethodInfo)null;
            Type type = objectType;
            do
            {
                MethodInfo method1 = type.GetMethod(method, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
                if (method1 != null)
                {
                    ParameterInfo[] parameters = method1.GetParameters();
                    int length = parameters.Length;
                    if (length > 0 && IsLastArray(parameters))
                    {
                        if (parameterCount >= length - 1)
                        {
                            methodInfo = method1;
                            break;
                        }
                    }
                    else if (length == parameterCount)
                    {
                        methodInfo = method1;
                        break;
                    }
                }
                type = type.BaseType;
            }
            while (type != (Type)null);
            return methodInfo;
        }

        /// <summary>
        /// 判断某个方法的形参列表中是否拥有一个可接受多个实际参数的数组类型参数：
        /// 方法只有一个数组类型的参数，或者方法的形参列表中的最后一个参数被标记为数组型。
        /// </summary>
        /// <param name="parameters">The parameters.</param>
        /// <returns></returns>
        internal static bool IsLastArray(ParameterInfo[] parameters)
        {
            int length = parameters.Length;
            return length > 0 && (length == 1 && parameters[0].ParameterType.IsArray || IsParamArray(parameters));
        }

        /// <summary>
        /// 判断某个方法的形参列表中的最后一个参数被标记为数组型(params)。
        /// </summary>
        /// <param name="parameters">The parameters.</param>
        /// <returns></returns>
        private static bool IsParamArray(ParameterInfo[] parameters)
        {
            int length = parameters.Length;
            return length > 0 && (uint)parameters[length - 1].GetCustomAttributes(typeof(ParamArrayAttribute), true).Length > 0U;
        }

        /// <summary>
        /// 表示一个空参数。
        /// </summary>
        public class NullParameter
        {
            /// <summary>
            /// 参数类型
            /// </summary>
            public Type ParameterType { get; set; }
        }
    }
}
